# -*- coding: utf-8 -*- 
# @Time : 2021/10/13 10:22 
# @Author : Orange
# @File : get_P_data.py


import datetime
import matplotlib.pyplot as plt
import datetime
import numpy as np
from sklearn.ensemble import IsolationForest
import pandas as pd
from constant_value import *
import threading
from P_zong.get_P_data import get_cec_and_elec


def diff_P_between_now_and_mean(data, date, time_list, flag):
    '''
    计算被限电时段的（功率实际值-加权平均功率）之和
    :param data: DataFrame结构
    :param date: 对应的日期的index，如"09-01"
    :param time_list: 对应的时间间隔的columns，如["00:00:00","00:06:00"]
    :param flag: 00:00:00本身是否为异常点标志位
    :return: 对应date的被限的总功率
    '''

    if flag == 0:  # 00:00:00本身不是异常点，是由1:00:00前推得到的，因此loc时，时间点应加1
        data.loc[date, time_list[0]] = 0
        data.loc["mean", time_list[0]] = 0
        data.loc["mean_sub_max_min", time_list[0]] = 0

    if data.loc["mean_sub_max_min", time_list[0]:time_list[-1]].isna().any():
        return abs(
            data.loc[date, time_list[0]:time_list[-1]].fillna(0) - data.loc["mean",
                                                                   time_list[0]:time_list[-1]]).sum() * 60
    else:
        return abs(data.loc[date, time_list[0]:time_list[-1]].fillna(0) - data.loc["mean_sub_max_min",
                                                                          time_list[0]:time_list[-1]]).sum() * 60


def weighted_mean(data, t1_start, t1_end, t2_start, t2_end):
    '''
    :param data: DataFrame结构
    :param t1_start:权重高的数据的开始日期
    :param t1_end:权重高的数据的截止日期
    :param t2_start:权重低的数据的开始日期
    :param t2_end:权重低的数据的截至日期
    :return: 新的data,增加了mean，25%和 mean_sub_max_min索引
    '''

    statistics = data.loc[t1_start:t1_end].describe()  # 保存基本统计量
    # IQR = statistics.loc['75%'] - statistics.loc['25%']  # 四分位数间距
    # QL = statistics.loc['25%']  # 下四分位数
    # QU = statistics.loc['75%']  # 上四分位数
    # threshold1 = QL - 1.5 * IQR  # 下阈值
    # threshold2 = QU + 1.5 * IQR  # 上阈值
    #
    # 所有数据按权重，忽略nan,max,min求平均
    f = lambda x: (x.sum() - x.max() - x.min()) / (len(x) - 2 - x.isna().sum() + 0.001)
    temp = data.loc[t1_start:t1_end].apply(f) * HIGH_WEIGHT + data.loc[t2_start:t2_end].apply(f) * LOW_WEIGHT

    data.loc['mean'] = data.mean()  # 所有数据按列求平均

    if temp.isna().any():
        if statistics.loc["mean"].isna().any():
            temp = data.loc['mean']
        else:
            temp = statistics.loc['mean']
    # 应对一些数据缺失的问题
    if statistics.loc['25%'].isna().any():
        data.loc['25%'] = data.quantile(0.25)
    else:
        data.loc['25%'] = statistics.loc['25%']  # 0901-0928的25%分位点
    # data.loc['50%'] = statistics.loc['50%']
    # data.loc['50%'] = statistics.loc['75%']
    # data.mean(axis=0, skipna=True)  # 计算30天同一时刻的均值，比如30天的00：00：00的功率均值
    # data.loc['threshold_low'] = threshold1
    # data.loc['threshold_high'] = threshold2
    # data.loc['IQR'] = IQR
    data.loc['mean_sub_max_min'] = temp
    return data


def continuous_time_in_list(time_list):
    '''
    :param time_list: 存放时间的列表，如[Timestamp('1900-01-01 15:00:00'), Timestamp('1900-01-01 16:00:00'), Timestamp('1900-01-01 17:00:00')]
    :return: 列表中所有的连续时间段[Timestamp('1900-01-01 15:00:00'), Timestamp('1900-01-01 17:00:00')]
    若检测出01:00:00时异常的，则实际异常时间段为00：00：00——01:00:00，因为取得是最近1h的平均值
    '''
    flag = 0
    t1 = []
    t2 = []
    for i in time_list:
        t1.append(i)
        if (i + datetime.timedelta(hours=1) not in time_list):
            if len(t1) > 1:
                if t1[0].strftime("%Y-%m-%d %H:%M:%S")[11:] == "00:00:00":  # 00:00:00本来就是异常的
                    t2.append([t1[0].strftime("%Y-%m-%d %H:%M:%S")[11:],
                               t1[-1].strftime("%Y-%m-%d %H:%M:%S")[11:]])
                    flag = 1
                else:
                    if len(t2) > 0 and int(t1[0].strftime("%Y-%m-%d %H:%M:%S")[11:13]) - int(
                            t2[-1][-1][:2]) == 2:  # 若异常时间之前只相差1h,则认为中间1H也异常
                        t2[-1][-1] = t1[-1].strftime("%Y-%m-%d %H:%M:%S")[11:]
                    else:

                        t2.append([(t1[0] - datetime.timedelta(hours=1)).strftime("%Y-%m-%d %H:%M:%S")[11:],
                                   t1[-1].strftime("%Y-%m-%d %H:%M:%S")[11:]])
            t1 = []
    return t2, flag


def isCurtailment(data, end_date, systemCode, mode, t_is_end, time_now):
    '''
    判断被限电的时段，以及被限的功率
    :param data: DataFrame结构的数据
    :return: '09-01': {'time': [['06:00:00', '23:00:00']], 'P': 23353.689901650887}, ...,'09-02': {'time': [['00:00:00', '07:00:00']], 'P': 22294.34929790695}}
    '''
    global flag, flag_del_key, key
    flag = 0
    flag_del_key = 0
    curtailment = {}  # 限电时间段字典

    if datetime.datetime.strptime(t_is_end, "%Y-%m-%d %H:%M:%S") > time_now:  # 判断时间在限电时间之前
        # 直接根据省限电时间判断，在省发布限电时间之前都是未限电
        curtailment["systemCode"] = systemCode
        curtailment["msg"] = "未被限电"
        return curtailment

    if (data.loc['mean'].dropna() < 20).all():  # 该企业24h，30天平均每小时的功率均小于30，则忽略该企业，因为该企业每天都以最低功率运行，不符合限电需求
        curtailment["systemCode"] = systemCode
        curtailment["msg"] = "未被限电"
        return curtailment
    # 计算30天的同一时刻的值除以30天的同一时刻的均值
    data_div_mean = data / data.loc['mean_sub_max_min']
    data_div_mean[data_div_mean < 0.5] = -1  # 若除以均值小于0.5，则认为异常，取值为-1
    data_div_mean[data_div_mean >= 0.5] = 1  # 若除以均值小于0.5，则认为异常，取值为 1
    data_div_mean.fillna(-1, inplace=True)  # 缺失值认为异常
    data_div_25 = data / (data.loc['25%'] + 0.1)  # 0.1是避免分母为0
    data_div_25[data_div_25 < 0.7] = -1  # 若除以25分位点小于0.5，则认为异常，取值为-1
    data_div_25[data_div_25 >= 0.7] = 1  # 若除以25分位点小于0.5，则认为异常，取值为 1
    data_div_25.fillna(-1, inplace=True)  # 缺失值认为异常
    data_div_25[abs(data - data.loc["25%"]) < 5] = 1  # 若某一时刻值与25%分位点差只<5，则认为正常，主要针对于小功率如1w(实时值)，2w(25%分位点)
    data_mean_and_25 = data_div_mean + data_div_25  # 相加都异常，结果为-2
    cols = data_mean_and_25.columns
    temp = data_mean_and_25.loc[::].apply(lambda x: x == -2)  #
    outliner_series = temp.apply(lambda x: list(cols[x.values]), axis=1)  # 将值为-2 的对应的columns取出来，按index放到列表里
    curtailment = {}  # 限电时间段字典
    for idx, val in enumerate(outliner_series):
        if len(val) > 1:  # 有2个及以上的异常值
            curtailment_time, flag = continuous_time_in_list(val)
            if curtailment_time:
                curtailment[outliner_series.index[idx]] = {}
                if data.loc[outliner_series.index[idx]].mean() < 1:  # 每天24h平均功率>1,则认为整天被限电
                    curtailment[outliner_series.index[idx]]["time"] = [["00:00:00", "23:00:00"]]
                else:

                    curtailment[outliner_series.index[idx]]["time"] = curtailment_time
    data.columns = [i.strftime("%Y-%m-%d %H:%M:%S")[11:] for i in data.columns]
    if mode == "multiple-dates":
        for key, val in curtailment.items():
            sum_P = 0  # 某天被限的总功率值
            for time_list in val["time"]:
                temp_P = diff_P_between_now_and_mean(data, key, time_list, flag)
                sum_P = sum_P + temp_P

            curtailment[key]["P"] = round(sum_P, 2)
        curtailment["systemCode"] = systemCode
    else:
        if not curtailment:  # 没有异常点，返回
            curtailment["systemCode"] = systemCode
            curtailment["msg"] = "未被限电"
        else:
            for key, val in curtailment.items():
                sum_P = 0  # 某天被限的总功率值
                for time_list in val["time"]:
                    temp_P = diff_P_between_now_and_mean(data, key, time_list, flag)
                    sum_P = sum_P + temp_P
                if sum_P / 60 < 20:
                    flag_del_key = 1
                else:
                    curtailment[key]["P"] = round(sum_P, 2)

        curtailment["systemCode"] = systemCode
        if flag_del_key == 1:
            curtailment.pop(key)
            curtailment["msg"] = "未被限电"

    return curtailment


def get_data_and_process(systemCode, start_time, end_time, stop_time, downsample):
    '''
    获取功率数据并处理成需要的格式
    :param systemCode: 企业名称
    :param start_time: 数仓取数据开始时间
    :param end_time: 数仓取数据结束时间
    :param downsample: 取样方式
    :return: 处理好的数据
    '''
    # try:
    #     # 首先取cec树的数据
    #     flag_elec_exit = 0
    #     cim_data = get_cim_data_cec(systemCode)
    #     device_id = getDeviceId(systemCode, cim_data)
    #     d_name, flag_P_is_exist = get_pointCodeForBigdata(cim_data, device_id)  # 获取业务树测点名称，不固定的
    #
    #
    #     if flag_P_is_exist == 1:  ## CEC未配置P
    #         flag_elec_exit = 1
    #         cim_data = get_cim_data(systemCode)  # 获取cim数据
    #         device_id = getDeviceId(systemCode, cim_data)  # 获取设备名称
    #         d_name, flag_P_is_exist = get_pointCodeForBigdata(cim_data, device_id)  # 获取业务树测点名称，不固定的
    #         if flag_P_is_exist:
    #             return "未配置P"
    #         P_DATAFRAME = P_data_process(device_id, systemCode, start_time, end_time, d_name,
    #                                      downsample)  # 获取功率数据 #若ELE获取数据为空，进入except
    #
    #     else:
    #         if flag_P_is_exist:
    #             return "未配置P"
    #         P_DATAFRAME = P_data_process(device_id, systemCode, start_time, end_time, d_name,
    #
    #                                      downsample)  # 获取功率数据 # 若CEC获取功率为空,进入except
    #
    #
    #
    #
    # except:
    #     if not flag_elec_exit:
    #         # 若上面报错，说明CEC没数据，从elec上取数据
    #         cim_data = get_cim_data(systemCode)  # 获取cim数据
    #         device_id = getDeviceId(systemCode, cim_data)  # 获取设备名称
    #         d_name, flag_P_is_exist = get_pointCodeForBigdata(cim_data, device_id)  # 获取业务树测点名称，不固定的
    #         if flag_P_is_exist:
    #             return "未配置P"
    #
    #         P_DATAFRAME = P_data_process(device_id, systemCode, start_time, end_time, d_name, downsample)  # 获取功率数据
    P_DATAFRAME = get_cec_and_elec(start_time, end_time, systemCode, downsample)
    if isinstance(P_DATAFRAME, str):
        return P_DATAFRAME
    """处理数据"""
    P_DATAFRAME['time'] = list(map(lambda x: datetime.datetime.strptime(x[11:], "%H:%M:%S"), P_DATAFRAME.index))
    data_groupby = P_DATAFRAME.groupby(['time'])['P'].apply(lambda x: x.values)  # 按照时间段分组
    idx = list(map(lambda x: x.strftime("%Y-%m-%d")[5:], pd.date_range(start=start_time[:10], end=end_time[:10])))
    data = pd.DataFrame(list(data_groupby.values), index=data_groupby.index, columns=idx)
    data[data < 0] = np.nan  # 对于功率值为负的数据，令他为nan
    data.dropna(axis=1, thresh=stop_time // 2, inplace=True)  # 丢弃缺失数据超过一半(一天24h，一半为12)的列
    if data.empty:  # 该企业无有效数据
        raise ValueError("该企业无有效数据")
    # data.fillna(method="bfill", inplace=True)  # 以临近值填充nan
    data = pd.DataFrame(data.values.T, index=data.columns, columns=data.index)  # 将data0转置
    return data


def get_result(systemCode, downsample, mode, province, createTime):
    '''

    :param systemCode: 企业名称
    :param downsample: 采样方式
    :param mode: 输出模式，mode="single-date"只输出本日数据；mode ="multiple-dates"输出7日内的数据
    :param province: 运维商底下的企业所在的省份
    :return: 限电时段，限电功率
    '''
    global result
    try:
        """时间信息"""
        # time_now = datetime.datetime.now()
        time_now = datetime.datetime.strptime(createTime, "%Y-%m-%d %H:%M:%S")
        start_time = "2021-07-01 00:00:00"  # 数仓数据开始时间
        try:
            t_is_end = WEIGHT1_DATE[province] + " 00:00:00"
        except:
            t_is_end = "2021-08-30 00:00:00"
        if datetime.datetime.strptime(t_is_end, "%Y-%m-%d %H:%M:%S") < time_now:
            end_time = time_now.strftime("%Y-%m-%d %H:%M:%S")
        else:
            end_time = t_is_end
        # end_time = WEIGHT1_DATE[province] + " 00:00:00"  # 数仓数据结束时间
        T2_START_TIME = (time_now - datetime.timedelta(days=10)).strftime("%Y-%m-%d %H:%M:%S")[5:10]  # 权重低的数据的开始日期
        T2_END_TIME = time_now.strftime("%Y-%m-%d %H:%M:%S")[5:10]  # 权重低的数据的截至日期

        # 判断截止时刻，只判断当日_当前时刻之前的时段是否被限电
        if mode == "single-date":
            stop_time = int(end_time[11:13])
        else:
            stop_time = 24

        """获取数据并处理"""
        data = get_data_and_process(systemCode, start_time, end_time, stop_time, downsample)  # 获取并处理数据
        if isinstance(data, str):
            return
        """权重1开始时间，结束时间"""
        try:
            # 根据各省限电政策得到的数据
            T1_END_TIME = WEIGHT1_DATE[province]
            temp = datetime.datetime.strptime(T1_END_TIME, "%Y-%m-%d") - datetime.timedelta(days=30)
            T1_START_TIME = temp.strftime("%Y-%m-%d %H:%M:%S")[5:10]
            T1_END_TIME = T1_END_TIME[5:]

        except:
            T1_START_TIME = "08-01"
            T1_END_TIME = "08-30"

        data = weighted_mean(data, T1_START_TIME, T1_END_TIME, T2_START_TIME, T2_END_TIME)  # 添加一些统计数据

        # new_data = data.loc[T2_END_TIME:, data.columns[:stop_time + 1]]

        """判断是否限电"""
        # # multiple_dates模式：长时间判断
        # single-date模式：当天判断\
        if mode == "single-date":
            new_data = data.loc[T2_END_TIME:, data.columns[:stop_time + 1]]
            curtailment = isCurtailment(data=new_data, end_date=T2_END_TIME, systemCode=systemCode, mode=mode,
                                        t_is_end=t_is_end, time_now=time_now)
        else:
            T_start = T1_END_TIME
            new_data = data.loc[T_start:, data.columns]
            curtailment = isCurtailment(data=new_data, end_date=T2_END_TIME, systemCode=systemCode, mode=mode,
                                        t_is_end=t_is_end, time_now=time_now)
        result.append(curtailment)
    except:
        result.append({"systemCode": systemCode, "msg": "缺乏有效数据"})


def call(arg, model, *args, **kwargs):
    systemCode = arg.get('systemCode')
    downsample = arg.get("downsample")
    mode = arg.get("mode")
    province = arg.get("province")
    createTime = arg.get("createTime")

    global result
    result = []

    threads = []  # 存放线程

    for idx, val in enumerate(systemCode):
        # 创建子线程并存放至列表threads
        l = threading.Thread(target=get_result, args=(val, downsample, mode, province[idx], createTime))
        threads.append(l)
    for t in threads:
        # 开启线程
        t.start()

    for t in threads:
        # 主线程等待子线程执行
        t.join()
    return {"data": result}


import time

t1 = time.time()

params = {"mode": "single-date", "downsample": "1h-avg-null", "province": "四川", "createTime": "2022-01-03 23:59:59",
 "systemCode": ["PARK932_EMS01"]}

result = call(params, model=None)
import json

json.dumps(result)
print(result)
t2 = time.time()
print(t2 - t1)

